package org.manusoft.refill.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;

import org.springframework.stereotype.Repository;

/**
 * Base class for DAO implementations.
 * 
 * @author vpaturet020408
 * 
 * @param <T>
 *            the persistent class this DAO apply to.
 * @param <ID>
 *            the class representing the unique id of the persistent class
 */
@Repository
public abstract class AbstractGenericJPADAO<T, ID extends Serializable>
		implements GenericDAO<T, ID> {
	/**
	 * The parameterized type of the persistent class.
	 */
	private Class<T> entityBeanType;
	/**
	 * The entity manager used to access the database.
	 */
	private EntityManager entityManager;

	/**
	 * Default constructor. Implementation note: initialize the reference to the
	 * parameterized type of the persistent class.
	 */

	@SuppressWarnings("unchecked")
	public AbstractGenericJPADAO() {
		this.entityBeanType = (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
	}

	/**
	 * @see GenericDAO#findById(Serializable, boolean)
	 */
	public T findById(ID id, boolean lock) {
		T entity;
		if (lock) {
			entity = getEntityManager().find(getEntityBeanType(), id);
			entityManager.lock(entity, javax.persistence.LockModeType.WRITE);
		} else {
			entity = getEntityManager().find(getEntityBeanType(), id);
		}
		return entity;
	}

	/**
	 * @see GenericDAO#findAll()
	 */
	@SuppressWarnings("unchecked")
	public List<T> findAll() {
		Query query = getEntityManager().createQuery(
				"from " + getEntityBeanType().getName());
		query.setHint("org.hibernate.cacheable", true);
		return query.getResultList();
	}

	/**
	 * @see GenericDAO#makePersistent(Object)
	 */
	public T makePersistent(T entity) {
		T t = getEntityManager().merge(entity);
		flush();
		return t;
	}

	/**
	 * @see GenericDAO#makeAllPersistent(List)
	 */
	public List<T> makeAllPersistent(List<T> entities) {
		List<T> mergedEntities = new ArrayList<T>(entities.size());
		for (T entity : entities) {
			mergedEntities.add(getEntityManager().merge(entity));
		}
		return mergedEntities;
	}

	/**
	 * @see GenericDAO#makeTransient(Object)
	 */
	public void makeTransient(T entity) {
		getEntityManager().remove(entity);
	}

	/**
	 * Flush the persistence context.
	 */
	public void flush() {
		getEntityManager().flush();
	}

	/**
	 * 
	 * @param entity
	 */
	public T merge(T entity) {
		T ent = getEntityManager().merge(entity);

		getEntityManager().flush();
		return ent;
	}

	/**
	 * 
	 * @param entity
	 */
	public void remove(T entity) {
		getEntityManager().remove(entity);
	}

	/**
	 * Clear the persistence context.
	 */
	public void clear() {
		getEntityManager().clear();
	}

	/**
	 * Returns the parameterized type of the persistent class.
	 * 
	 * @return the parameterized type of the persistent class.
	 */
	public Class<T> getEntityBeanType() {
		return entityBeanType;
	}

	/**
	 * Set the parameterized type of the persistent class.
	 * 
	 * @param entityBeanType
	 *            the parameterized type of the persistent class.
	 */
	public void setEntityBeanType(Class<T> entityBeanType) {
		this.entityBeanType = entityBeanType;
	}

	/**
	 * Returns the entity manager used to access the database.
	 * 
	 * @return entity manager used to access the database
	 */
	protected EntityManager getEntityManager() {
		if (entityManager == null) {
			throw new IllegalStateException(
					"EntityManager has not been set on DAO before usage");
		}
		return entityManager;
	}

	/**
	 * Sets the entity manager used to access the database.
	 * 
	 * @param em
	 *            the entity manager used to access the database.
	 */
	@PersistenceContext(unitName  = "refill")
	public void setEntityManager(EntityManager em) {
		this.entityManager = em;

	}

	/**
	 * Sets the query cache.
	 * 
	 * @param query
	 * 
	 */
	protected void setCacheable(Query query) {
		query.setHint("org.hibernate.cacheable", true);
	}

	/**
	 * Sets Sets the readOnly to true.
	 * 
	 * @param em
	 *            the entity manager used to access the database.
	 */
	protected void setReadOnly(Query query) {
		query.setHint("org.hibernate.readOnly", true);
	}
}
